//
// class AgentExploringWorld - This is the world as is being seen by the Agent
//
package classes;

import java.util.Vector;

public class AgentExploringWorld {
	/*
	 * All changes in Agent
	 * */
	
	public AgentExploringCell[][] agentWorld;
	private int size = WorldCreation.size;
	private Agent agent;
	
	public AgentExploringWorld(Agent a) {
		// need to make 2 dimentional array of AgentExploringCell
		// initialization
		agentWorld = new AgentExploringCell[size][size];
		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				agentWorld[i][j] = new AgentExploringCell(this,i,j);
			}
		}
		agent = a;
	}
	
	public void setPerceptsAndObjects(Point p, int [] percepts, char [] objects) {
	  // Loop through percepts: 
	  // 0: Breeze
	  // 1: Stench
	  // 2: Glitter
	  for (int i=0; i<3; i++) {
	    agentWorld[p.getX()][p.getY()].percept[i] = percepts[i];
	    
	    // update probabilites of all cells for that percept
	    updateProbabilities(p, i, percepts[i]);
	  }

    // mark our cell visited and discovered
    agentWorld[p.getX()][p.getY()].isVisited = true;
    agentWorld[p.getX()][p.getY()].isDiscovered = true;

    // update map if agent can figure out on the agent's map 
    // some assumptions with help of received perceptions
    if (objects[0]=='P') agentWorld[p.getX()][p.getY()].putPit();
    else if (objects[1]=='W') agentWorld[p.getX()][p.getY()].putWampus();
    else if (objects[2]=='G') {
      agentWorld[p.getX()][p.getY()].putGold();
      agent.setHasGold(true);
    }
    else if (objects[4]=='B') agentWorld[p.getX()][p.getY()].putBat();
    else if (objects[5]=='E') agentWorld[p.getX()][p.getY()].putExit();
    else agentWorld[p.getX()][p.getY()].putNothing();
    
    normalizeMap(p);
 }
	
	private void normalizeMap(Point p) {
	  if (agentWorld[agent.getX()][agent.getY()].isSure[2] && 
	      agentWorld[agent.getX()][agent.getY()].objectProb[2]==1) {

	    // Reset all gold positions if Agent is in the Gold cell
	    for (int i=0; i<size; i++)
	      for (int j=0; j<size; j++) {
	        agentWorld[i][j].isSure[2]=true;
	        agentWorld[i][j].objectProb[2]=0;
	      }
	  }
	}
	
  public void updateProbabilities(Point p, int perceptType, int number) {
    int knownPresent = 0;
    int notSure = 4;
    int remaining = 0;
    
    // array with adjustment cells
    AgentExploringCell [] cellsAround = arrayOfCellsAround(agentWorld[p.getX()][p.getY()]);
    for (int i = 0; i < cellsAround.length; i++)
      if (cellsAround[i].isSure[perceptType]) {
        if (cellsAround[i].objectProb[perceptType]==1) knownPresent++;
        notSure--;
      }
    
    remaining = number - knownPresent;
    
    for (int i = 0; i < cellsAround.length; i++) {
      if (!cellsAround[i].isSure[perceptType]) 
        cellsAround[i].objectProb[perceptType]+=(remaining/(float)notSure);
      cellsAround[i].isDiscovered = true;
      if (number==0) {
        cellsAround[i].objectProb[perceptType] = 0;
        cellsAround[i].isSure[perceptType] = true;
      }
    }
  }
  
  public AgentExploringCell [] arrayOfCellsAround(AgentExploringCell c) {
    AgentExploringCell [] cellsAround = new AgentExploringCell [4];
    int xLocation = c.getX();
    int yLocation = c.getY();
    
    cellsAround[0] = agentWorld[xLocation][(yLocation+1)%size];      // North
    cellsAround[1] = agentWorld[(xLocation+1)%size][yLocation];      // East
    cellsAround[2] = agentWorld[xLocation][(yLocation-1+size)%size]; // South
    cellsAround[3] = agentWorld[(xLocation-1+size)%size][yLocation]; // West
    
    return cellsAround;
  }
   
  // Returns the cells with high probability of having Gold/Exit
	public Vector<AgentExploringCell> highTargetProb(boolean exit) {

	  Vector<AgentExploringCell> v = new Vector<AgentExploringCell>();

		for (int i = 0; i < agentWorld.length; i++)
			for (int j = 0; j < agentWorld.length; j++)
			  if (agentWorld[i][j].isDiscovered) {
			    if (v.size()==0) v.add(agentWorld[i][j]);
			    else {
			      int k;
            for (k = 0; k<v.size(); k++)
              if (exit && agentWorld[i][j].isExit || !exit && 
                  (agentWorld[i][j].objectProb[2] > v.elementAt(k).objectProb[2])) {
                v.insertElementAt(agentWorld[i][j], k);
                break;
              }
            if (k==v.size()) v.add(agentWorld[i][j]);
          }
			  }
		
    // take only the highest probability cells
    Vector<AgentExploringCell> vOut = new Vector<AgentExploringCell>();
    
    if (v.size()>0) {
      float prob = v.get(0).objectProb[2];
      boolean isExit = v.get(0).isExit;
      vOut.add(v.get(0));
      for (int i=1; i<v.size(); i++)
        if (exit && isExit==v.get(i).isExit || !exit && prob==v.get(i).objectProb[2])
          vOut.add(v.get(i));
        else break;
    }
      
    return vOut;
	}

	// get the direction to go between visited cells on the way from c0 to c1
  public char getVisitedWay(AgentExploringCell c0, AgentExploringCell c1) {
    
    boolean [][] visited = new boolean[size][size];
    for (int i=0; i<size; i++)
      for (int j=0; j<size; j++)
        visited[i][j]=false;
    
    Vector<AgentExploringCell> v = new Vector<AgentExploringCell>();
    
    getVisitedPath(visited, v, c0, c1);
    
    // there must be at least 2 cells in v
    if (v.size()<2) {
      System.out.println("Error in getVisitedPath()!");
      return ' ';
    }

    AgentExploringCell [] around = arrayOfCellsAround(c0);
    int i;
    for (i=0; i<4; i++) if(around[i]==v.get(1)) break;
    switch (i)
    {
      case 0: return 'N';
      case 1: return 'E';
      case 2: return 'S';
      case 3: return 'W';
      default: System.out.println("Error in getVisitedWay");
    }
    
    return ' ';
  }
  
	// get the list of cells that makes the path between cell c0 and cell c1 
	// using only visited cells
	public boolean getVisitedPath(boolean [][] visited, Vector<AgentExploringCell> v,
	    AgentExploringCell c0, AgentExploringCell c1) {
	  // visited must be an array of size*size of boolean initialised to false
	  // v must not be null
	  
	  v.add(c0);
	  
	  if (c0==c1) return true; // we found it
	  AgentExploringCell [] around = arrayOfCellsAround(c0);
	  visited[c0.getX()][c0.getY()] = true;
	  
	  for (int i=0; i<4; i++)
	    if (around[i]==c1 || around[i].isVisited && 
	        !visited[around[i].getX()][around[i].getY()])
	      if (getVisitedPath (visited, v, around[i], c1)) return true;
	  
	  v.remove(c0);	  
	  return false;
	}
	
  // returns the distance between 2 cells
  public int getDistance(AgentExploringCell c0, AgentExploringCell c1) {
    int xDist = Math.abs(c0.getX()-c1.getX());
    if (xDist > size/2) xDist = size - xDist;
    int yDist = Math.abs(c0.getY()-c1.getY());
    if (yDist > size/2) yDist = size - yDist;
    return xDist + yDist;
  }
  
	// returns the v0 vector sorted with the closest cells to any of the cells in v1
	public Vector<AgentExploringCell> getClosestPath(Vector<AgentExploringCell> v0,
	                                                        Vector<AgentExploringCell> v1) {

	  int [] min = new int[v0.size()];
	  for (int i=0; i<v0.size(); i++) min[i] = Integer.MAX_VALUE;
    for (int i=0; i<v0.size(); i++)
      for (int j=0; j<v1.size(); j++) {
        int d = getDistance(v0.get(i),v1.get(j));
        if (d < min[i]) min[i] =d;
      }
        	  
    Vector<AgentExploringCell> v = new Vector<AgentExploringCell>();
    Vector<Integer> vDist = new Vector<Integer>();
    for (int i=0; i<v0.size(); i++)
      if (v.size()==0) { v.add(v0.get(i)); vDist.add(min[i]); }
      else {
        int j;
        for (j = 0; j<v.size(); j++)
          if (min[i] < vDist.get(j)) {
            v.insertElementAt(v0.get(i), j);
            vDist.insertElementAt(min[i], j);
            break;
          }
        if (j==v.size()) { v.add(v0.get(i)); vDist.add(min[i]); }
      }

    // take only the lowest distant cells
    Vector<AgentExploringCell> vOut = new Vector<AgentExploringCell>();
    
    if (v.size()>0) {
      int dist = vDist.get(0);
      vOut.add(v.get(0));
      for (int i=1; i<v.size(); i++)
        if (dist == vDist.get(i))
          vOut.add(v.get(i));
        else break;
    }
      
    return vOut;
	}
	
	// returns the visited cells that are accessible from cell c
  public Vector<AgentExploringCell> getVisitedIsland(Vector<AgentExploringCell> v0, AgentExploringCell c) {

    Vector<AgentExploringCell> v;
    if (v0==null) v = new Vector<AgentExploringCell>();
    else v = v0;
    
    // the parameter point has to be a visited cell
    if (c==null || !c.isVisited || v.contains(c)) return v;
    else {
      v.add(c);
      AgentExploringCell [] around = arrayOfCellsAround(c);
      for (int i=0; i<4; i++)
        getVisitedIsland(v, around[i]);
    }

    return v;
  }
  
  // gets all the unvisited cells that are in the border with the visited cells
  public Vector <AgentExploringCell> getUnvisitedBorder(Vector<AgentExploringCell> vV) {

    Vector<AgentExploringCell> v = new Vector<AgentExploringCell>();
    
    if (vV==null) return v;
    
    // the parameter point has to be a visited cell
    for (int i=0; i<vV.size(); i++) {
      AgentExploringCell [] around = arrayOfCellsAround(vV.elementAt(i));
      for (int j=0; j<4; j++)
        if (!around[j].isVisited && !v.contains(around[j])) v.add(around[j]);
    }

    return v;
  }

  // gets the cells with the lowest risk for stepping on a pit or wampus,
  // according to the probabilities
  public Vector <AgentExploringCell> getLowestRisk(Vector <AgentExploringCell> v0) {

    Vector<AgentExploringCell> v = new Vector<AgentExploringCell>();
    if (v0==null) return v;
    for (int i=0; i<v0.size(); i++)
      if (v.size()==0) v.add(v0.get(i));
      else {
        int j;
        for (j=0; j<v.size(); j++)
          if (v0.get(i).objectProb[0]+v0.get(i).objectProb[1] < 
              v.get(j).objectProb[0]+v.get(j).objectProb[1]) {
            v.insertElementAt(v0.get(i), j);
            break;
          }
        if (j==v.size()) v.add(v0.get(i));
      }
    
    // take only the lowest risk cells
    Vector<AgentExploringCell> vOut = new Vector<AgentExploringCell>();
    
    if (v.size()>0) {
      float prob = v.get(0).objectProb[0]+v.get(0).objectProb[1];
      vOut.add(v.get(0));
      for (int i=1; i<v.size(); i++)
        if (prob == v.get(i).objectProb[0]+v.get(i).objectProb[1])
          vOut.add(v.get(i));
        else break;
    }
      
    return vOut;
  }
}

